# -*- coding: utf-8 -*-
# @Time : 2023/09/17 12:49
# @Author : yangyongzhen
# @Email : 534117529@qq.com
# @File : stompclienttool.py
# @Project : study
import time
import os
from tkinter.ttk import *
from tkinter import *
from datetime import datetime
from tkinter import messagebox
from ttkbootstrap import Style
#import stomp
import json
#import websocket
from PIL import Image, ImageTk
import stomp_ws

global gui  # 全局型式保存GUI句柄

tx_cnt = 0  # 发送条数统计
rx_cnt = 0  # 接收条数统计


class GUI:
    def __init__(self):
        self.root = Tk()
        self.root.title('STOMP协议客户端调试助手-author:blog.csdn.net/qq8864')  # 窗口名称
        self.root.geometry("820x560+500+150")  # 尺寸位置
        self.root.resizable(False, False)
        self.interface()
        #主题修改 可选['cyborg', 'journal', 'darkly', 'flatly' 'solar', 'minty', 'litera', 'united', 'pulse', 'cosmo', 'lumen', 'yeti', 'superhero','sandstone']
        Style(theme='minty')
        self.isConnect = False
        self.client = None

    def interface(self):
        """"界面编写位置"""
        # 操作区域
        self.fr1 = Frame(self.root)
        self.fr1.place(x=0, y=0, width=220, height=600)  # 区域1位置尺寸
        img_path = os.path.join(os.path.dirname(__file__), 'me.png')
        img = Image.open(img_path)  # 替换为你的图片路径
        img = img.resize((80, 80))
        self._img = ImageTk.PhotoImage(img)
        self.about = Label(self.fr1)
        self.about.image = self._img
        self.about.configure(image=self._img)
        self.about.place(x=65, y=0, width=80, height=80)

        pos = 80
        self.lb_server = Label(self.fr1, text='地址:', anchor="e", fg='red')
        self.lb_server.place(x=0, y=pos, width=50, height=35)
        self.txt_server = Text(self.fr1)
        self.txt_server.place(x=65, y=pos, width=155, height=28)
        self.txt_server.insert("1.0", "ws://localhost:15674/ws")  # WebSocket 地址

        self.lb_port = Label(self.fr1, text='clientID:', anchor="e", fg='red')
        self.lb_port.place(x=0, y=pos + 40, width=50, height=35)
        self.txt_id = Text(self.fr1)
        self.txt_id.place(x=65, y=pos + 40, width=155, height=28)
        self.txt_id.insert("1.0", "stomp-client")

        self.lb_user = Label(self.fr1, text='用户名:', anchor="e", fg='red')
        self.lb_user.place(x=0, y=pos + 80, width=50, height=35)
        self.txt_name = Text(self.fr1)
        self.txt_name.place(x=65, y=pos + 80, width=155, height=28)
        self.txt_name.insert("1.0", "guest")

        self.lb_pwd = Label(self.fr1, text='密码:', anchor="e", fg='red')
        self.lb_pwd.place(x=0, y=pos + 120, width=50, height=35)
        self.txt_pwd = Text(self.fr1)
        self.txt_pwd.place(x=65, y=pos + 120, width=155, height=28)
        self.txt_pwd.insert("1.0", "guest")

        self.var_bt1 = StringVar()
        self.var_bt1.set("连接")
        self.btn1 = Button(self.fr1, textvariable=self.var_bt1, command=self.btn_connect)
        self.btn1.place(x=170, y=pos + 160, width=50, height=30)

        self.lb_s = Label(self.fr1, text='订阅主题', bg="yellow", anchor='w')
        self.lb_s.place(x=5, y=340, width=90, height=28)

        self.txt_sub = Text(self.fr1)
        self.txt_sub.place(x=5, y=368, width=155, height=28)
        self.btn5 = Button(self.fr1, text='订阅', command=self.btn_sub)
        self.btn5.place(x=170, y=368, width=50, height=28)

        self.subitem = Listbox(self.fr1)
        self.subitem.place(x=5, y=402, width=215, height=85)
        self.subitem.bind("<Button-3>", self.on_right_click)

        # 文本区域
        self.fr2 = Frame(self.root)
        self.fr2.place(x=220, y=0, width=620, height=560)

        self.txt_rx = Text(self.fr2)
        self.txt_rx.place(relheight=0.6, relwidth=0.9, relx=0.05, rely=0.01)

        self.scrollbar = Scrollbar(self.txt_rx)
        self.scrollbar.pack(side=RIGHT, fill=Y)
        self.txt_rx.config(yscrollcommand=self.scrollbar.set)
        self.scrollbar.config(command=self.txt_rx.yview)
        self.txt_rx.bind("<Configure>", self.check_scrollbar)

        self.lb_t = Label(self.fr2, text='发布主题', bg="yellow", anchor='w')
        self.lb_t.place(relheight=0.04, relwidth=0.2, relx=0.05, rely=0.62)

        self.txt_topic = Text(self.fr2)
        self.txt_topic.place(relheight=0.05, relwidth=0.9, relx=0.05, rely=0.66)

        self.txt_tx = Text(self.fr2)
        self.txt_tx.place(relheight=0.15, relwidth=0.9, relx=0.05, rely=0.72)

        self.btn3 = Button(self.fr2, text='清空',command = self.txt_clr) #绑定清空方法
        self.btn4 = Button(self.fr2, text='保存',command=self.savefiles) #绑定保存方法
        self.btn3.place(relheight=0.06,relwidth=0.11,relx=0.05,rely=0.88)
        self.btn4.place(relheight=0.06,relwidth=0.11,relx=0.18,rely=0.88)
        
        self.btn6 = Button(self.fr2, text='发送', command=self.btn_send)
        self.btn6.place(relheight=0.06, relwidth=0.11, relx=0.84, rely=0.88)

        self.lb3 = Label(self.fr2, text='接收:0    发送:0', bg="yellow", anchor='w')
        self.lb3.place(relheight=0.05, relwidth=0.3, relx=0.045, rely=0.945)

        self.lb4 = Label(self.fr2, text=' ', anchor='w',relief=GROOVE)  #时钟
        self.lb4.place(relheight=0.05, relwidth=0.11, relx=0.84, rely=0.945)

    def check_scrollbar(self, *args):
        if self.txt_rx.yview() == (0.0, 1.0):
            self.scrollbar.pack_forget()
        else:
            self.scrollbar.place(RIGHT, fill=Y)

    def on_right_click(self, w):
        idx = self.subitem.curselection()
        if idx == ():
            return
        selected_item = self.subitem.get(idx)
        ret = messagebox.askyesno('取消订阅', "取消订阅:\n" + selected_item)
        if ret:
            self.subitem.delete(idx)
            self.client.unsubscribe(selected_item)
            self.appendTxt("取消订阅:" + selected_item)

    def gettim(self):#获取时间 未用
            timestr = time.strftime("%H:%M:%S")  # 获取当前的时间并转化为字符串
            self.lb4.configure(text=timestr)  # 重新设置标签文本
            # tim_str = str(datetime.datetime.now()) + '\n'
            # self.lb4['text'] = tim_str
            #self.lb3['text'] = '接收：'+str(rx_cnt),'发送：'+str(tx_cnt)
            self.txt_rx.after(1000, self.gettim)     # 每隔1s调用函数 gettime 自身获取时间 GUI自带的定时函数
 
    def txt_clr(self):#清空显示
        self.txt_rx.delete(0.0, 'end')  # 清空文本框
        self.txt_tx.delete(0.0, 'end')  # 清空文本框

    def tx_rx_cnt(self,rx=0,tx=0):  #发送接收统计
        global tx_cnt
        global rx_cnt
 
        rx_cnt += rx
        tx_cnt += tx
        self.lb3['text'] = '接收：'+str(rx_cnt),'发送：'+str(tx_cnt)
 
    def savefiles(self):   #保存日志TXT文本
        try:
            with open('log.txt','a') as file:       #a方式打开 文本追加模式
                file.write(self.txt_rx.get(0.0,'end'))
                messagebox.showinfo('提示', '保存成功')
        except:
            messagebox.showinfo('错误', '保存日志文件失败！')
    
    def log_callback(self,client, userdata, level, buf):
        print(buf)

    def is_valid_json(self,json_str):
        """
        判断字符串是否是有效的 JSON
        Args:
            json_str (str): 需要判断的字符串
        Returns:
            bool: 如果字符串是有效的 JSON，则返回 True，否则返回 False
        """
        if json_str is None:
            return False
        try:
            json.loads(json_str)
            return True
        except ValueError:
            return False
			
    def appendTxt(self, msg, flag=None):
        current_t = datetime.now()
        current_ = current_t.strftime("%Y-%m-%d %H:%M:%S ")
        self.txt_rx.insert(END, current_)
        self.txt_rx.insert(END, msg)
        self.txt_rx.insert(END, "\n")
        self.txt_rx.see(END)
        self.txt_rx.update_idletasks()

    def connect(self, ws_url, user, password):
        # 将 ws_url 分解成 (host, port) 形式的元组
        if ws_url.startswith("ws://"):
            ws_url = ws_url[5:]
        elif ws_url.startswith("wss://"):
            ws_url = ws_url[6:]
        else:
            raise ValueError("Invalid WebSocket URL")

        self.client = stomp_ws.Stomp(ws_url, sockjs=False, wss=False)
        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message
        self.client.on_error = self.on_error
        self.client.on_close = self.on_close
        if self.client.dispatcher.ws.keep_running:
            self.isConnect = self.client.connect(user,password)
        return self.isConnect

    def on_connect(self, rc):
        if rc == True:
            print("Connected to Stomp Broker ok!\n")
            self.appendTxt("Connected to Stomp Broker ok!\n")
            self.var_bt1.set("断开")
            self.isConnect = True
        else:
            print("Failed to connect, return code %d\n", rc)
            self.appendTxt(f"Failed to connect\n")
            self.isConnect = False
 
    def on_message(self, cmd,header, body):
        self.tx_rx_cnt(1,0)
        print("Received message: \n" + str(header))
        header = json.loads(str(header).replace("'", '"'))
        header = json.dumps(header, indent=4, sort_keys=True, separators=(',', ': '), ensure_ascii=False)
            
        if(self.is_valid_json(body)):
            body = json.loads(str(body).replace("'", '"'))
            body = json.dumps(body, indent=4, sort_keys=True, separators=(',', ': '), ensure_ascii=False)
        self.appendTxt(f"Received message:\n[Cmd]:{cmd}\n[Header]:\n{header}\n[Body]:\n{body}\n","RECV")

    def on_error(self, error):
        self.appendTxt(f"发生错误: {error}")

    def on_close(self,code,reason):
        self.isConnect = False
        self.var_bt1.set("连接")
        self.subitem.delete(0, END)
        self.appendTxt("WebSocket连接已关闭,code="+ str(code) +',reason='+reason)

    def btn_connect(self):  # 连接
        if self.var_bt1.get() == '连接':
            server = self.txt_server.get("1.0", END).strip()
            user = self.txt_name.get("1.0", END).strip()
            psd = self.txt_pwd.get("1.0", END).strip()
            ws_url = server  # WebSocket 地址
            print(f"连接到 {ws_url}，用户名: {user}")
            self.appendTxt(f"连接到 {ws_url}，用户名: {user}")
            if self.connect(ws_url, user, psd):
                self.appendTxt(f"连接到 {ws_url} 成功!")
                self.var_bt1.set("断开")
            else:
                self.appendTxt(f"连接到 {ws_url} 失败!")
        else:
            self.client.disconnect()
            self.var_bt1.set("连接")
            self.isConnect = False
            self.appendTxt("断开连接！")

    def btn_sub(self):  # 订阅
        if self.isConnect:
            sub = self.txt_sub.get("1.0", END).strip()
            self.client.subscribe(destination=sub, ack='auto')
            self.appendTxt(f"已订阅主题: {sub}")
            self.subitem.insert(END, sub)
        else:
            messagebox.showinfo('提示', '服务器未连接!')

    def btn_send(self):  # 发布
        if self.isConnect:
            pub_topic = self.txt_topic.get("1.0", END).strip()
            payload = self.txt_tx.get("1.0", END).strip()
            self.client.send(destination=pub_topic,message=payload)
            self.appendTxt(f"发布到 {pub_topic}: {payload}")
            self.tx_rx_cnt(0,1)
        else:
            messagebox.showinfo('提示', '请连接服务器!')

if __name__ == '__main__':
    print('Start...')
    gui = GUI()
    gui.gettim()  #开启时钟
    gui.root.mainloop()
    print('End...')
